{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Routes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Behaviour in FastHTML apps is defined by routes. The syntax is largely the same as the wonderful [FastAPI](https://fastapi.tiangolo.com/) (which is what you should be using instead of this if you're creating a JSON service. FastHTML is mainly for making HTML web apps, not APIs).\n",
    "\n",
    ":::{.callout-warning title=\"Unfinished\"}\n",
    "We haven't yet written complete documentation of all of FastHTML's routing features -- until we add that, the best place to see all the available functionality is to look over [the tests](/api/core.html#tests)\n",
    ":::\n",
    "\n",
    "Note that you need to include the types of your parameters, so that `FastHTML` knows what to pass to your function. Here, we're just expecting a string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from fasthtml.common import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app = FastHTML()\n",
    "\n",
    "@app.get('/user/{nm}')\n",
    "def get_nm(nm:str): return f\"Good day to you, {nm}!\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Normally you'd save this into a file such as main.py, and then run it in `uvicorn` using:\n",
    "\n",
    "```\n",
    "uvicorn main:app\n",
    "```\n",
    "\n",
    "However, for testing, we can use Starlette's `TestClient` to try it out:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from starlette.testclient import TestClient"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Response [200 OK]>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client = TestClient(app)\n",
    "r = client.get('/user/Jeremy')\n",
    "r"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "TestClient uses `httpx` behind the scenes, so it returns a `httpx.Response`, which has a `text` attribute with our response body:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Good day to you, Jeremy!'"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "r.text"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the previous example, the function name (`get_nm`) didn't actually matter -- we could have just called it `_`, for instance, since we never actually call it directly. It's just called through HTTP. In fact, we often do call our functions `_` when using this style of route, since that's one less thing we have to worry about, naming.\n",
    "\n",
    "An alternative approach to creating a route is to use `app.route` instead, in which case, you make the function name the HTTP method you want. Since this is such a common pattern, you might like to give a shorter name to `app.route` -- we normally use `rt`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Going postal!'"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rt = app.route\n",
    "\n",
    "@rt('/')\n",
    "def post(): return \"Going postal!\"\n",
    "\n",
    "client.post('/').text"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Route-specific functionality"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "FastHTML supports custom decorators for adding specific functionality to routes. This allows you to implement authentication, authorization, middleware, or other custom behaviors for individual routes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's an example of a basic authentication decorator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Protected Content'"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from functools import wraps\n",
    "\n",
    "def basic_auth(f):\n",
    "    @wraps(f)\n",
    "    async def wrapper(req, *args, **kwargs):\n",
    "        token = req.headers.get(\"Authorization\")\n",
    "        if token == 'abc123':\n",
    "            return await f(req, *args, **kwargs)\n",
    "        return Response('Not Authorized', status_code=401)\n",
    "    return wrapper\n",
    "\n",
    "@app.get(\"/protected\")\n",
    "@basic_auth\n",
    "async def protected(req):\n",
    "    return \"Protected Content\"\n",
    "\n",
    "client.get('/protected', headers={'Authorization': 'abc123'}).text"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The decorator intercepts the request before the route function executes. If the decorator allows the request to proceed, it calls the original route function, passing along the request and any other arguments."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One of the key advantages of this approach is the ability to apply different behaviors to different routes. You can also stack multiple decorators on a single route for combined functionality."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "App level beforeware\n",
      "Route level beforeware\n",
      "Second route level beforeware\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'Users Page'"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def app_beforeware():\n",
    "    print('App level beforeware')\n",
    "\n",
    "app = FastHTML(before=Beforeware(app_beforeware))\n",
    "client = TestClient(app)\n",
    "\n",
    "def route_beforeware(f):\n",
    "    @wraps(f)\n",
    "    async def decorator(*args, **kwargs):\n",
    "        print('Route level beforeware')\n",
    "        return await f(*args, **kwargs)\n",
    "    return decorator\n",
    "    \n",
    "def second_route_beforeware(f):\n",
    "    @wraps(f)\n",
    "    async def decorator(*args, **kwargs):\n",
    "        print('Second route level beforeware')\n",
    "        return await f(*args, **kwargs)\n",
    "    return decorator\n",
    "\n",
    "@app.get(\"/users\")\n",
    "@route_beforeware\n",
    "@second_route_beforeware\n",
    "async def users():\n",
    "    return \"Users Page\"\n",
    "\n",
    "client.get('/users').text"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This flexiblity allows for granular control over route behaviour, enabling you to tailor each endpoint's functionality as needed. While app-level beforeware remains useful for global operations, decorators provide a powerful tool for route-specific customization."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Combining Routes\n",
    "\n",
    "Sometimes a FastHTML project can grow so weildy that putting all the routes into `main.py` becomes unweildy. Or, we install a FastHTML- or Starlette-based package that requires us to add routes. \n",
    "\n",
    "First let's create a `books.py` module, that represents all the user-related views:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# books.py\n",
    "books_app, rt = fast_app()\n",
    "\n",
    "books = ['A Guide to FastHTML', 'FastHTML Cookbook', 'FastHTML in 24 Hours']\n",
    "\n",
    "@rt(\"/\", name=\"list\")\n",
    "def get():\n",
    "    return Titled(\"Books\", *[P(book) for book in books])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's mount it in our main module:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "from books import app as books_app\n",
    "\n",
    "app, rt = fast_app(routes=[Mount(\"/books\", books_app, name=\"books\")]) # <1>\n",
    "\n",
    "@rt(\"/\")\n",
    "def get():\n",
    "    return Titled(\"Dashboard\",\n",
    "        P(A(href=\"/books\")(\"Books\")),  # <2>\n",
    "        Hr(),\n",
    "        P(A(link=uri(\"books:list\"))(\"Books\")),  # <3>\n",
    "    )\n",
    "\n",
    "serve()\n",
    "```\n",
    "\n",
    "1. We use `starlette.Mount` to add the route to our routes list. We provide the name of `books` to make discovery and management of the links easier. More on that in items 2 and 3 of this annotations list\n",
    "2. This example link to the books list view is hand-crafted. Obvious in purpose, it makes changing link patterns in the future harder\n",
    "3. This example link uses the named URL route for the books. The advantage of this approach is it makes management of large numbers of link items easier.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
